﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using System.Dynamic;
using System.IO;
using System.Collections.Concurrent;
using System.Threading;
using System.Diagnostics.Contracts;
using System.Collections;
using System.Reflection;
using System.Diagnostics;

using King.Extensions;
using King.Text;
using King.IO;
using King.Loader;
using King.Reflection;
using King.Collections;
using King.CSharp.Reflection;
using King.Xml.IO;
using King.Xml.Reflection;
using King.Xml.Reflection.Emit;
using King.Xml.CSharp;
using King.Xml.CSharp.Reflection;
using King.Xml.CSharp.Reflection.Emit;
using King.Rest;
using King.Rest.Xml.CSharp;
using King.Rest.Reflection;
using King.Rest.Reflection.Emit;
using King.Rest.Xml.CSharp.Reflection;
using King.Rest.Xml.CSharp.Reflection.Emit;
using System.Xml;
using System.Text.RegularExpressions;

namespace King.Rest.Xml.CSharp {

    // any type attributed with XsSchemaAttribute can be loaded as a RestXmlSchema
    public abstract class RestXmlAttribute : Attribute {
        internal RestXmlAttribute() { }
    }

    // services contain messages
    public sealed class RestXmlMessageAttribute : RestXmlAttribute {
        public Type Type { get; set; }
        public string Name { get; set; }
    }

    // complex types contain properties
    public sealed class RestXmlComplexTypeAttribute : RestXmlAttribute {
        public RestXmlComplexTypeAttribute() {
        }
        public bool IsAbstract { get; set; }
        public bool IsRestriction { get; set; }
        public string Name { get; set; }
    }
    public sealed class RestXmlPropertyAttribute : RestXmlAttribute {
        private int m_order;

        public RestXmlPropertyAttribute() {
        }
        public RestXmlPropertyAttribute(int order) {
            m_order = order;
        }

        public int Order {
            get { return m_order; }
        }

        public string Name { get; set; }
        public object Default { get; set; }
        public Type Type { get; set; }
        public bool IsAttribute { get; set; }
        public bool IsStreamable { get; set; }
        public bool IsOptional { get; set; }
    }

    // groups
    public sealed class RestXmlAttributeGroupAttribute : RestXmlAttribute {
    }
    public sealed class RestXmlPropertyGroupAttribute : RestXmlAttribute {
    }

    public abstract class SimpleContent {
        public object Value { get; set; }
    }
    public abstract class SimpleContent<T> : SimpleContent
        where T : Xs.AnyType {
    }

    [XsSchema(RestXmlLoader.RestNamespace)]
    public static class Rs {
        public const string Namespace = RestXmlLoader.RestNamespace;
        public const string DefaultPrefix = RestXmlLoader.RestDefaultPrefix;
        public static readonly XPrefix DefaultXPrefix = (XPrefix)DefaultPrefix;
        public static readonly XNamespace XNamespace = (XNamespace)Namespace;

        [RestXmlComplexType]
        public class List { }

        [RestXmlComplexType]
        public class Any { }
    }
}
namespace King.Rest.Xml.CSharp.Reflection {
    public sealed class RestXmlCSharpLoaderException : Exception {
        internal RestXmlCSharpLoaderException(string message)
            : base(message) {
        }
    }

    /// <summary>
    /// Use XsCSharpLoader to load assemblies and simple types and augment them
    /// with complex types used for REST style communication.
    /// </summary>
    public sealed class RestXmlCSharpLoader {

        private readonly XsCSharpLoader m_xsCSharpLoader;
        private readonly RestXmlLoader m_restXmlLoader;

        private readonly Loader<XsCSharpAssembly, RestXmlCSharpAssembly> m_assemblyLoader;
        private readonly Loader<CSharpType, RestXmlCSharpSchema> m_schemaLoader;

        public RestXmlCSharpLoader(
            CSharpLoader csharpLoader = null,
            Func<XNamespace, CSharpType> getSchemaType = null) {

            if (csharpLoader == null)
                csharpLoader = CSharpLoader.Create();

            // teach XsCSharpSchemas how to load ComplexTypes
            m_xsCSharpLoader = new XsCSharpLoader(
                csharpLoader: csharpLoader, 
                schemaFactory: factory =>
                    m_schemaLoader.Get(factory.CSharpType)
                    .CreateXsCSharpSchema(factory),

                getSchemaType: o => 
                    o == Rs.XNamespace ? CSharpLoader.GetType(typeof(Rs)) :
                    getSchemaType != null ? getSchemaType(o) : null
            );

            // teach RestXmlSchemas to use custom serilaizers
            m_restXmlLoader = new RestXmlLoader(XsLoader, o => {
                    var schema = GetSchema(o.XsSchema);
                    return schema.CreateRestXmlSchema(o);
                },

                getType: o => {
                    if (o == null)
                        return null;

                    var result = GetType(o.GetType());
                    if (result == null)
                        return null;

                    return result.RestXmlType;
                },
                getSchema: o => {
                    if (o == null)
                        return null;

                    var result = GetSchema(o.GetType());
                    if (result == null)
                        return null;

                    return result.RestXmlSchema;
                }
           );

            m_assemblyLoader = new Loader<XsCSharpAssembly, RestXmlCSharpAssembly>(
                keys: null,
                defaultResolver: o => new RestXmlCSharpAssembly(this, o.Key),
                getLoader: o => o.Loader.m_assemblyLoader,
                getKeys: o => new object[] {
                    o.XsCSharpAssembly
                },
                normalizeKey: o => {
                    if (o is XsCSharpAssembly)
                        return o as XsCSharpAssembly;

                    return null;
                }
            );

            m_schemaLoader = new Loader<CSharpType, RestXmlCSharpSchema>(
                keys: null,
                defaultResolver: o => new RestXmlCSharpSchema(this, o.Key),
                getLoader: o => o.Loader.m_schemaLoader,
                getKeys: o => new object[] {
                    o.CSharpType,
                },
                normalizeKey: o => (CSharpType)o
            );
        }

        public CSharpLoader CSharpLoader {
            get { return XsCSharpLoader.CSharpLoader; }
        }
        public XsLoader XsLoader {
            get { return XsCSharpLoader.XsLoader; }
        }
        public XsCSharpLoader XsCSharpLoader {
            get { return m_xsCSharpLoader; }
        }
        public RestXmlLoader RestXmlLoader {
            get { return m_restXmlLoader; }
        }

        public RestXmlCSharpAssembly GetAssembly(XsCSharpAssembly xsCSharpAssembly) {
            return m_assemblyLoader.TryGet(xsCSharpAssembly);
        }
        public RestXmlCSharpAssembly GetAssembly(CSharpAssembly csharpAssembly) {
            var xsCSharpAssembly = XsCSharpLoader.GetAssembly(csharpAssembly);
            return GetAssembly(xsCSharpAssembly);
        }

        public RestXmlCSharpSchema GetSchema(XsCSharpSchema xsCSharpSchema) {
            if (xsCSharpSchema == null)
                return null;

            return m_schemaLoader.TryGet(xsCSharpSchema.CSharpType);
        }
        public RestXmlCSharpSchema GetSchema(XNamespace xNamespace) {
            if (xNamespace == null)
                return null;

            var xsCSharpSchema = XsCSharpLoader.GetSchema(xNamespace);

            return GetSchema(xsCSharpSchema);
        }
        public RestXmlCSharpSchema GetSchema(XsSchema xsSchema) {
            if (xsSchema == null)
                return null;

            var xsCSharpSchema = XsCSharpLoader.GetSchema(xsSchema);

            return GetSchema(xsCSharpSchema);
        }
        public RestXmlCSharpSchema GetSchema(RestXmlSchema restXmlSchema) {
            if (restXmlSchema == null)
                return null;

            var xsSchema = restXmlSchema.XsSchema;
            var xsCSharpSchema = XsCSharpLoader.GetSchema(xsSchema);

            return GetSchema(xsCSharpSchema);
        }
        public RestXmlCSharpSchema GetSchema(Type type) {
            if (type == null)
                return null;

            var xsCSharpSchema = XsCSharpLoader.GetSchema(type);

            return GetSchema(xsCSharpSchema);
        }
        public RestXmlCSharpSchema GetSchema(CSharpType csharpType) {
            if (csharpType == null)
                return null;

            var xsCSharpSchema = XsCSharpLoader.GetSchema(csharpType);

            return GetSchema(xsCSharpSchema);
        }

        public RestXmlCSharpType GetType(CSharpType csharpType) {
            if (csharpType == null)
                return null;

            // list alias
            if (csharpType.Implements(typeof(IEnumerable)) && csharpType.IsInterface)
                return GetType(typeof(Rs.List));

            // object alias
            if (csharpType == typeof(object))
                return GetType(typeof(Rs.Any));

            // resolve schema
            var schemaType = csharpType.DeclaringType;
            var xsSchema = XsCSharpLoader.GetSchema(schemaType);
            var schema = GetSchema(xsSchema);
            if (schema == null) 
                return null;

            return schema.GetType(csharpType);
        }
        public RestXmlCSharpType GetType(Type type) {
            var csharpType = CSharpLoader.TryGetType(type);
            return GetType(csharpType);
        }
        public RestXmlCSharpType GetType(string assemblyQualifiedName) {
            var csharpType = CSharpLoader.TryGetType(assemblyQualifiedName);
            return GetType(csharpType);
        }
        public RestXmlCSharpType GetType(XsCSharpType xsCSharpType) {
            if (xsCSharpType == null)
                return null;

            var xsCSharpSchema = xsCSharpType.Schema;
            var schema = GetSchema(xsCSharpSchema);
            return schema.GetType(xsCSharpType);
        }
        public RestXmlCSharpType GetType(XName xName) {

            // resolve schema
            var xNamespace = xName.Namespace;
            var schema = GetSchema(xNamespace);
            if (schema == null)
                return null;

            return schema.GetType(xName);
        }
        public RestXmlCSharpType GetType(XsType xsType) {
            if (xsType == null)
                return null;

            var xName = xsType.XName;
            return GetType(xName);
        }
        public RestXmlCSharpType GetType(RestXmlType restXmlType) {
            if (restXmlType == null)
                return null;

            var xName = restXmlType.XName;
            return GetType(xName);
        }

        public Task<dynamic> Deserialize(TextReader stream) {
            return RestXmlLoader.Deserialize(stream);
        }
        public Task<dynamic> Deserialize(string xml) {
            return RestXmlLoader.Deserialize(xml);
        }

        public string Serialize(object value) {
            return RestXmlLoader.Serialize(value);
        }
        public Task Serialize(object value, XmlWriter xmlWriter) {
            return RestXmlLoader.Serialize(value, xmlWriter);
        }
    }
    public sealed class RestXmlCSharpAssembly : RestXmlCSharpLoaderMember {

        internal RestXmlCSharpAssembly(
            RestXmlCSharpLoader loader,
            XsCSharpAssembly xsCSharpAssembly)
            : base(loader, new Lazy<XsCSharpAssembly>(() => xsCSharpAssembly)) {
        }

        private RestXmlCSharpType GetType(RestXmlCSharpType type) {
            if (type == null || type.Assembly != this)
                return null;
            return type;
        }
        private RestXmlCSharpSchema GetSchema(RestXmlCSharpSchema schema) {
            if (schema == null || XsCSharpAssembly != schema.XsCSharpAssembly)
                return null;
            return schema;
        }

        public IEnumerable<RestXmlCSharpSchema> Schemas() {
            return XsCSharpAssembly.Schemas().Select(o => GetSchema(o));
        }
        public RestXmlCSharpSchema GetSchema(CSharpType csharpType) {
            var schema = Loader.GetSchema(csharpType);
            return GetSchema(schema);
        }
        public RestXmlCSharpSchema GetSchema(XNamespace xNamespace) {
            var schema = Loader.GetSchema(xNamespace);
            return GetSchema(schema);
        }
        public RestXmlCSharpSchema GetSchema(XsSchema xsSchema) {
            var schema = Loader.GetSchema(xsSchema);
            return GetSchema(schema);
        }
        public RestXmlCSharpSchema GetSchema(XsCSharpSchema xsCSharpSchema) {
            var schema = Loader.GetSchema(xsCSharpSchema);
            return GetSchema(schema);
        }
        public RestXmlCSharpSchema GetSchema(RestXmlSchema restXmlSchema) {
            var schema = Loader.GetSchema(restXmlSchema);
            return GetSchema(schema);
        }

        public IEnumerable<RestXmlCSharpType> Types() {
            return Schemas().SelectMany(o => o.Types());
        }
        public RestXmlCSharpType GetType(XName xName) {
            var type = Loader.GetType(xName);
            return GetType(type);
        }
        public RestXmlCSharpType GetType(CSharpType csharpType) {
            var type = Loader.GetType(csharpType);
            return GetType(type);
        }
        public RestXmlCSharpType GetType(XsType xsType) {
            var type = Loader.GetType(xsType);
            return GetType(type);
        }
        public RestXmlCSharpType GetType(XsCSharpType xsCSharpType) {
            var type = Loader.GetType(xsCSharpType);
            return GetType(type);
        }
        public RestXmlCSharpType GetType(RestXmlType restXmlType) {
            var type = Loader.GetType(restXmlType);
            return GetType(type);
        }
        public RestXmlCSharpType GetType(Type clrType) {
            var type = Loader.GetType(clrType);
            return GetType(type);
        }
        public RestXmlCSharpType GetType(string fullName) {
            var clrType = CSharpAssembly.GetType(fullName);
            return GetType(clrType);
        }
    }
    public sealed class RestXmlCSharpSchema : RestXmlCSharpAssemblyMember {

        private readonly RestXmlCSharpLoader m_loader;
        private readonly CSharpType m_schemaType;
        private readonly Lazy<XNamespace> m_xNamespace;
        private readonly Lazy<XsCSharpSchema> m_xsCSharpSchema;
        private readonly Loader<CSharpType, RestXmlCSharpType> m_typeLoader;
        private readonly Loader<CSharpMember, RestXmlCSharpProperty> m_messages;

        internal RestXmlCSharpSchema(RestXmlCSharpLoader loader, CSharpType schemaType)
            : base(loader, 
            null,
            o => o.XsCSharpLoader.GetSchema(schemaType)) {

            m_loader = loader;
            m_schemaType = schemaType;
            m_xsCSharpSchema = new Lazy<XsCSharpSchema>(() => XsCSharpLoader.GetSchema(schemaType));
            m_xNamespace = new Lazy<XNamespace>(() => XsSchema.XNamespace);

            var propertyNames = RestXmlCSharpResolver.GetPropertyNames(
                schemaType,
                typeof(RestXmlMessageAttribute)
            );

            m_messages = new Loader<CSharpMember, RestXmlCSharpProperty>(
                keys: o => propertyNames.Keys,

                defaultResolver: o => new RestXmlCSharpProperty(this, o.Key),
                getLoader: o => o.Schema.m_messages,
                getKeys: o => new object[] {
                    o.Name,
                    o.XName,
                    o.XsElement,
                    o.CSharpMember
                },
                normalizeKey:  o =>
                    RestXmlCSharpResolver.ResolveProperty(
                        key: o, 
                        schemaType: CSharpType, 
                        propertyNames: propertyNames, 
                        xNamespace: XNamespace, 
                        toCSharpName: ToCSharpName, 
                        xsForm: ElementFormDefault, 
                        typesWithXNameProperty: new[] {
                            typeof(XsCSharpElement), 
                            typeof(XsElement)
                    })
            );

            m_typeLoader = new Loader<CSharpType, RestXmlCSharpType>(
                keys: o => XsCSharpSchema.Types().Select(x => x.CSharpType),
                defaultResolver: o => {

                    var type = o.Key;

                    // test if type is a complex type
                    var complexType = ResolveComplexType(type);
                    if (complexType != null) 
                        return new RestXmlCSharpComplexType(this, complexType);

                    // try get a xs type
                    var xsCSharpType = XsCSharpSchema.GetType(type);
                    if (xsCSharpType == null)
                        return null;

                    // xs types
                    return new RestXmlXsCSharpType(this, xsCSharpType);
                },

                getLoader: o => o.Schema.m_typeLoader,
                getKeys: o => new object[] {
                    o.CSharpType,
                    o.Alias,
                    o.Name,
                    o.XName,
                },
                normalizeKey: o =>
                    XsCSharpResolver.ResolveNestedType(o, schemaType, XNamespace, ToCSharpName)
            );
        }

        private bool IsRest {
            get { return XNamespace == RestXmlLoader.RestXNamespace; }
        }

        internal RestXmlSchema CreateRestXmlSchema(RestXmlSchemaFactory factory) {

            // teach custom deserialization
            return factory.CreateSchema(
                typeFactory: o => {
                    var type = GetType(o.XsType);
                    return type.CreateRestXmlType(o);
                },
                activate: () => m_schemaType.Load().Activate()
            );
        }
        internal XsCSharpSchema CreateXsCSharpSchema(XsCSharpSchemaFactory factory) {

            // teach ComplexType, AttributeGroup, and RootElement loading
            return factory.CreateSchema(

                // teach ComplexType loading
                complexTypeNames: o => ComplexTypeNames(),
                complexTypeFactory: o => {

                    // ensure type is a ComplexType
                    var csharpType = ResolveComplexType(o.CSharpType);
                    if (csharpType == null)
                        return null;

                    return GetType(o.CSharpType).CreateXsCSharpType(o);
                },

                // teach AttributeGroup loading
                attributeGroupNames: null,
                attributeGroupFactory: null,

                // teach RootElement loading
                rootElementNames: o => Messages().Select(x => x.Name).ToList(),
                rootElementFactory: o => {
                    var message = GetMessage(o.Name);
                    if (message == null)
                        return null;

                    return message.CreateRootXsElement(o);
                }
            );
        }
        internal IEnumerable<CSharpType> ComplexTypeNames() {
            var result =
                from type in CSharpAssembly.Types()
                let attr = type.GetAttribute(typeof(RestXmlComplexTypeAttribute))
                where attr != null
                where (CSharpType)attr.Schema == CSharpType
                select type;

            return result;
        }
        internal CSharpType ResolveComplexType(CSharpType type) {

            if (type == null)
                return null;

            // verify RestXmlComplexTypeAttribute attribute
            if (!type.IsDefined(typeof(RestXmlComplexTypeAttribute)))
                return null;

            if (type == null)
                return null;

            // verify DeclaringType
            if (type.DeclaringType != XsCSharpSchema.CSharpType)
                return null;

            return type;
        }

        public override XNamespace XNamespace {
            get { return m_xNamespace.Value; }
        }
        public XsForm ElementFormDefault {
            get { return XsSchema.ElementFormDefault; }
        }
        public XsForm AttributeFormDefault {
            get { return XsSchema.AttributeFormDefault; }
        }
        
        public string ToXLocalName(string name) {
            return XsCSharpSchema.ToXLocalName(name);
        }
        public string ToCSharpName(string name) {
            return XsCSharpSchema.ToCSharpName(name);
        }
        public XName ToXName(string name, XsForm xsForm = XsForm.Qualified) {
            return XsCSharpSchema.ToXName(name, xsForm);
        }

        public CSharpType CSharpType {
            get { return m_schemaType; }
        }
        public IEnumerable<RestXmlCSharpType> Types() {
            return m_typeLoader.Values();
        }

        public RestXmlCSharpType GetType(CSharpType csharpType) {
            return m_typeLoader.TryGet(csharpType);
        }
        public RestXmlCSharpType GetType(XsCSharpType xsCSharpType) {
            if (xsCSharpType == null)
                return null;
            return m_typeLoader.TryGet(xsCSharpType.CSharpType);
        }
        public RestXmlCSharpType GetType(XName xName) {
            return m_typeLoader.TryGet(xName);
        }
        public RestXmlCSharpType GetType(string name) {
            return m_typeLoader.TryGet(name);
        }
        public RestXmlCSharpType GetType(XsType xsType) {
            if (xsType == null)
                return null;

            var xName = xsType.XName;
            return GetType(xsType.XName);
        }
        public RestXmlCSharpType GetType(RestXmlType restXmlType) {
            if (restXmlType == null)
                return null;

            var xName = restXmlType.XName;
            return GetType(xName);
        }
        public RestXmlCSharpType GetType(Type type) {
            if (type == null)
                return null;

            var csharpType = CSharpLoader.GetType(type);
            return GetType(csharpType);
        }

        public IEnumerable<RestXmlCSharpProperty> Messages() {
            return m_messages.Values();
        }
        public RestXmlCSharpProperty GetMessage(string name) {
            return m_messages.TryGet(name);
        }
        public RestXmlCSharpProperty GetMessage(XName xName) {
            return m_messages.TryGet(xName);
        }
        public RestXmlCSharpProperty GetMessage(XsElement xsElement) {
            return m_messages.TryGet(xsElement);
        }
        public RestXmlCSharpProperty GetMessage(XsCSharpElement xsCSharpElement) {
            return m_messages.TryGet(xsCSharpElement);
        }
        public RestXmlCSharpProperty GetMessage(RestXmlProperty restXmlProperty) {
            if (restXmlProperty == null)
                return null;
            return m_messages.TryGet(restXmlProperty.XName);
        }

        public override string ToString() {
            return XNamespace.ToString();
        }
    }

    public abstract class RestXmlCSharpType : RestXmlCSharpSchemaMember {
        private readonly RestXmlCSharpSchema m_schema;

        internal RestXmlCSharpType(RestXmlCSharpSchema schema) 
            : base(schema) {
            m_schema = schema;
        }
        internal abstract RestXmlType CreateRestXmlType(RestXmlTypeFactory factory);
        internal abstract XsCSharpType CreateXsCSharpType(XsCSharpTypeFactory typeFactory);

        public abstract CSharpType CSharpType {
            get;
        }
        public virtual XsCSharpType XsCSharpType {
            get { return XsCSharpLoader.GetType(CSharpType); }
        }
        public RestXmlType RestXmlType {
            get { return RestXmlSchema.GetType(XName); }
        }
        public XsType XsType {
            get { return XsCSharpType.XsType; }
        }
        public virtual CSharpType Alias {
            get { return null; }
        }

        public virtual RestXmlCSharpType BaseType {
            get { return null; }
        }

        public abstract XName XName { get; }
        public abstract string Name { get; }

        public bool IsComplexType {
            get { return XsCSharpType.IsComplexType; }
        }
        public bool IsSimpleType {
            get { return XsCSharpType.IsSimpleType; }
        }
        public abstract bool IsAbstract {
            get;
        }
        public abstract bool IsRestriction {
            get;
        }

        public virtual RestXmlCSharpProperty SimpleContent {
            get { return null; }
        }
        public virtual IEnumerable<RestXmlCSharpProperty> Properties() {
            return Enumerable.Empty<RestXmlCSharpProperty>();
        }
        public virtual RestXmlCSharpProperty GetProperty(string name) {
            return null;
        }
        public virtual RestXmlCSharpProperty GetProperty(XName xName) {
            return null;
        }
        public virtual RestXmlCSharpProperty GetProperty(CSharpProperty property) {
            return null;
        }
        public virtual RestXmlCSharpProperty GetProperty(XsCSharpElement xsCSharpElement) {
            return null;
        }
        public virtual RestXmlCSharpProperty GetProperty(XsCSharpAttribute xsCSharpAttribute) {
            return null;
        }
        public virtual RestXmlCSharpProperty GetProperty(XsElement xsElement) {
            return null;
        }
        public virtual RestXmlCSharpProperty GetProperty(XsAttribute xsAttribute) {
            return null;
        }
        public virtual RestXmlCSharpProperty GetProperty(RestXmlCSharpProperty restXmlCSharpProperty) {
            return null;
        }

        public override string ToString() {
            return XName.ToString();
        }
    }
    internal sealed class RestXmlXsCSharpType : RestXmlCSharpType {
        private readonly XsCSharpType m_xsCSharpType;
        private readonly Lazy<RestXmlCSharpType> m_baseType;

        public RestXmlXsCSharpType(RestXmlCSharpSchema schema, XsCSharpType xsCSharpType)   
            : base(schema) {

            m_xsCSharpType = xsCSharpType;
            m_baseType = new Lazy<RestXmlCSharpType>(() => Loader.GetType(XsType.BaseType));
        }
        internal override RestXmlType CreateRestXmlType(RestXmlTypeFactory factory) {
            if (IsSimpleType)
                return factory.CreateSimpleType(o => 
                    XsCSharpType.Deserialize(o));

            // ur types
            return factory.CreateComplexType();
        }
        internal override XsCSharpType CreateXsCSharpType(XsCSharpTypeFactory typeFactory) {
            // XsCSharpLoader should load simple types
            throw new InvalidOperationException();
        }

        public override XName XName {
            get { return XsCSharpType.XName; }
        }
        public override string Name {
            get { return XsCSharpType.Name; }
        }
        public override RestXmlCSharpType BaseType {
            get { return m_baseType.Value; }
        }
        public override CSharpType Alias {
            get { return XsCSharpType.Alias; }
        }
        public override CSharpType CSharpType {
            get { return m_xsCSharpType.CSharpType; }
        }
        public override XsCSharpType XsCSharpType {
            get { return m_xsCSharpType; }
        }
        public override bool IsAbstract {
            get { return m_xsCSharpType.IsAbstract; }
        }
        public override bool IsRestriction {
            get { return m_xsCSharpType.IsRestriction; }
        }
    }
    internal sealed class RestXmlCSharpComplexType : RestXmlCSharpType {
        private readonly CSharpType m_csharpType;
        private readonly RestXmlCSharpProperty m_simpleContent;
        private readonly Loader<CSharpMember, RestXmlCSharpProperty> m_properties;
        private readonly Lazy<RestXmlCSharpType> m_baseType;

        private readonly bool m_isAbstract;
        private readonly bool m_isRestriction;

        public RestXmlCSharpComplexType(
            RestXmlCSharpSchema schema, 
            CSharpType csharpType,
            RestXmlCSharpType contentType = null)
            : base(schema) {

            m_csharpType = csharpType;

            var attr = m_csharpType.GetAttribute(typeof(RestXmlComplexTypeAttribute));
            m_isAbstract = (bool)attr.IsAbstract;
            m_isRestriction = (bool)attr.IsRestriction;

            var propertyNames = RestXmlCSharpResolver.GetPropertyNames(
                m_csharpType,
                typeof(RestXmlPropertyAttribute)
            );

            // properties
            m_properties = new Loader<CSharpMember, RestXmlCSharpProperty>(
                keys: o => propertyNames.Keys,

                defaultResolver: o => new RestXmlCSharpProperty(this, o.Key),
                getLoader: o => ((RestXmlCSharpComplexType)o.DeclaringType).m_properties,
                getKeys: o => new object[] {
                    o.Name,
                    o.XName,
                    o.CSharpMember,
                    o.XsCSharpElement,
                    o.XsCSharpAttribute,
                    o.XsElement,
                    o.XsAttribute
                },
                normalizeKey: o => 
                    RestXmlCSharpResolver.ResolveProperty(
                        key: o, 
                        schemaType: m_csharpType, 
                        propertyNames: propertyNames, 
                        xNamespace: XNamespace, 
                        toCSharpName: Schema.ToCSharpName, 
                        xsForm: Schema.ElementFormDefault, 
                        typesWithXNameProperty: new[] {
                            typeof(XsCSharpElement), 
                            typeof(XsCSharpAttribute), 
                            typeof(XsElement), 
                            typeof(XsAttribute) 
                        }),

                getBaseType: o => {
                    var baseType = BaseType as RestXmlCSharpComplexType;
                    if (baseType == null)
                        return null;
                    return baseType.m_properties;
                },
                getOverride: o => o.BaseProperty
            );

            // content property
            if (CSharpType.GetGenericSubClass(typeof(SimpleContent<>)) != null)
                m_simpleContent = new RestXmlCSharpProperty(this);

            // base type
            m_baseType = new Lazy<RestXmlCSharpType>(() => {
                var csharpBaseType = CSharpType.BaseType;
                ASSERT.Condition(csharpBaseType != null);

                if (csharpBaseType == typeof(object))
                    return Loader.GetType(XsLoader.AnyType);

                if (csharpBaseType == typeof(Attribute))
                    return Loader.GetType(XsLoader.AnyType);

                if (csharpBaseType.GenericTypeDefinition == typeof(SimpleContent<>))
                    return Loader.GetType(XsLoader.AnyType);

                var type = Loader.GetType(csharpBaseType);
                if (type == null)
                    throw new RestXmlCSharpLoaderException(
                        ("Type '{0}' has a base type '{1}' that " + 
                        "failed to resolve to a RestXmlType.")
                        .Substitute(CSharpType, csharpBaseType));

                return Loader.GetType(type.XsType);
            });
        }

        internal override RestXmlType CreateRestXmlType(RestXmlTypeFactory factory) {

            // teach activate
            return factory.CreateComplexType(
                activate: () => m_csharpType.Load().Activate(),

                // teach property serialization
                propertyFactory: o => {

                    // content property
                    if (o.XName == null)
                        return SimpleContent.CreateRestXmlProperty(o);

                    // named property
                    var name = Schema.ToCSharpName(o.Name);
                    var property = GetProperty(name);
                    ASSERT.Condition(property != null);
                    return property.CreateRestXmlProperty(o);
                }
            );
        }
        internal override XsCSharpType CreateXsCSharpType(XsCSharpTypeFactory typeFactory) {

            var complexContent = Properties().Where(o => !o.IsContent && !o.IsAttribute).ToArray();
            var attributes = Properties().Where(o => o.IsAttribute).ToArray();

            Func<XsAttributeFactory, IEnumerable<object>> attributeFactory =
                xsAttributeFactory =>
                    attributes.Select(o => 
                        o.CreateXsAttribute(xsAttributeFactory));

            // CreateComplexTypeWithSimpleContent
            if (m_simpleContent != null) {

                if (complexContent.Any())
                    throw new RestXmlCSharpLoaderException(
                        "Type '{0}' defined both simple and complex content."
                        .Substitute(this));

                return typeFactory.CreateComplexTypeWithSimpleContent(
                    baseType: BaseType.XsCSharpType,
                    alias: null,

                    isAbstract: IsAbstract,
                    final: XsFinal.None,

                    attributeFactory: attributeFactory,
                    simpleContent: o => {

                        XsCSharpType baseType = null;

                        // inherit simpleContent base type
                        if (BaseType.SimpleContent != null)
                            baseType = BaseType.SimpleContent.XsCSharpPropertyType;

                        // baseType is generic argument of SimpleContent<>
                        else
                            baseType = XsCSharpLoader.GetType(
                                CSharpType.GetGenericSubClass(typeof(SimpleContent<>))
                                .GenericArguments().First());

                        return o.CreateSimpleContent(CSharpType, XsCSharpType, baseType);
                    },

                    metadata: null
                );
            }

            // CreateComplexTypeWithComplexContent
            return typeFactory.CreateComplexTypeWithComplexContent(
                baseType: BaseType.XsCSharpType,
                alias: null,

                isAbstract: IsAbstract,
                isRestriction: IsRestriction,
                isMixed: false,
                final: XsFinal.None,

                attributeFactory: attributeFactory,
                contentFactory: xsMemberFactory =>

                    // sequence envelope
                    xsMemberFactory.CreateSequence(xsSequenceMemberFactory =>
                        complexContent.Select(property => 
                            property.CreateXsElement(xsSequenceMemberFactory)
                        )
                    ),

                    metadata: null
            );
        }

        public override XName XName {
            get { return XNamespace + Schema.ToXLocalName(Name); }
        }
        public override string Name {
            get { return CSharpType.Name; }
        }

        public override RestXmlCSharpType BaseType {
            get { return m_baseType.Value; }
        }
        public override CSharpType CSharpType {
            get { return m_csharpType; }
        }
        public override bool IsAbstract {
            get { return m_isAbstract; }
        }
        public override bool IsRestriction {
            get { return m_isRestriction; }
        }

        public override RestXmlCSharpProperty SimpleContent {
            get { return m_simpleContent; }
        }
        public override IEnumerable<RestXmlCSharpProperty> Properties() {
            var result = m_properties.Values();

            if (m_simpleContent != null)
                result = result.Concat(m_simpleContent);

            return result;
        }
        public override RestXmlCSharpProperty GetProperty(string name) {
            return m_properties.TryGet(name);
        }
        public override RestXmlCSharpProperty GetProperty(XName xName) {
            return m_properties.TryGet(xName);
        }
        public override RestXmlCSharpProperty GetProperty(CSharpProperty property) {
            return m_properties.TryGet(property);
        }
        public override RestXmlCSharpProperty GetProperty(XsCSharpElement xsCSharpElement) {
            return m_properties.TryGet(xsCSharpElement);
        }
        public override RestXmlCSharpProperty GetProperty(XsCSharpAttribute xsCSharpAttribute) {
            return m_properties.TryGet(xsCSharpAttribute);
        }
        public override RestXmlCSharpProperty GetProperty(XsElement xsElement) {
            return m_properties.TryGet(xsElement);
        }
        public override RestXmlCSharpProperty GetProperty(XsAttribute xsAttribute) {
            return m_properties.TryGet(xsAttribute);
        }
        public override RestXmlCSharpProperty GetProperty(RestXmlCSharpProperty restXmlCSharpProperty) {
            if (restXmlCSharpProperty == null)
                return null;
            return GetProperty(restXmlCSharpProperty.XName);
        }
    }
    
    public sealed class RestXmlCSharpProperty : RestXmlCSharpSchemaMember {

        private readonly RestXmlCSharpType m_declaringType;
        private readonly CSharpType m_csharpPropertyType;
        private readonly Lazy<RestXmlCSharpType> m_propertyType;
        private readonly CSharpMember m_csharpMember;
        private readonly string m_name;
        private readonly XName m_xName;
        
        private readonly bool m_isAttribute;
        private readonly bool m_isContent;
        private readonly bool m_isStreamable;
        private readonly bool m_isOptional;
        private readonly object m_defaultValue;

        private readonly Lazy<RestXmlProperty> m_restXmlProperty;

        // content
        public RestXmlCSharpProperty(
            RestXmlCSharpType declaringType
        ) : base(declaringType.Schema) {
        
            m_declaringType = declaringType;
            m_isContent = true;

            // the simple content type
            m_propertyType = new Lazy<RestXmlCSharpType>(() => {
                var xsSimpleContent = m_declaringType.XsCSharpType.SimpleContent;

                // it may be anonymous
                if (xsSimpleContent.XName == null)
                    return new RestXmlXsCSharpType(Schema, xsSimpleContent);

                return Loader.GetType(xsSimpleContent);
            });

            // bind to SimpleContent.Value for this generic instantiation
            m_csharpMember = CSharpLoader.GetType(typeof(SimpleContent))
                .GetProperty("Value", isPublic: true, isStatic: false);
        }

        // message
        public RestXmlCSharpProperty(RestXmlCSharpSchema schema, CSharpMember csharpMember)
            : this(schema, typeof(RestXmlMessageAttribute), csharpMember) {

            m_isOptional = true;

            m_restXmlProperty = new Lazy<RestXmlProperty>(() => {
                return RestXmlSchema.GetMessage(XName.LocalName); 
            });
        }

        // element/attribute
        public RestXmlCSharpProperty(RestXmlCSharpType type, CSharpMember csharpMember)
            : this(type.Schema, typeof(RestXmlPropertyAttribute), csharpMember) {
            
            m_declaringType = type;

            var attr = csharpMember.GetAttribute(typeof(RestXmlPropertyAttribute));
            m_isAttribute = attr.IsAttribute;
            m_isStreamable = attr.IsStreamable;
            m_isOptional = attr.IsOptional;
            m_defaultValue = attr.Default;

            m_restXmlProperty = new Lazy<RestXmlProperty>(() => 
                DeclaringType.RestXmlType.GetProperty(XName.LocalName)
            );
        }

        // common constructor
        private RestXmlCSharpProperty(
            RestXmlCSharpSchema schema, 
            Type attributeType,
            CSharpMember csharpMember
        ) : base(schema) {

            m_csharpMember = csharpMember;
            var attribute = m_csharpMember.GetAttribute(attributeType);

            // property name
            m_name = (string)attribute.Name ?? m_csharpMember.Name;
            m_xName = Schema.ToXName(m_name, XsSchema.ElementFormDefault);
            
            // propertyType from attribute
            m_csharpPropertyType = (CSharpType)attribute.Type;

            // propertyType from field or property
            if (m_csharpPropertyType == null && m_csharpMember is CSharpDataMember)
                m_csharpPropertyType = ((CSharpDataMember)m_csharpMember).MemberType;

            // propertyType from method
            if (m_csharpPropertyType == null && m_csharpMember is CSharpMethod)
                m_csharpPropertyType = ((CSharpMethod)m_csharpMember).ReturnType;

            // must have a propertyType
            ASSERT.Condition(m_csharpPropertyType != null);

            // load propertyType
            m_propertyType = new Lazy<RestXmlCSharpType>(() => {
                var type = Schema.GetType(m_csharpPropertyType) ??
                    Loader.GetType(m_csharpPropertyType);

                if (type == null)
                    throw new RestXmlCSharpLoaderException(
                        "RestXmlCSharpSchema '{0}' could not resolve type '{1}'."
                        .Substitute(Schema, m_csharpPropertyType)
                    );

                return type;
            });
        }

        internal XsAttribute CreateXsAttribute(XsAttributeFactory xsAttributeFactory) {
            ASSERT.Condition(m_isAttribute);

            // <xs:anyAttribute />
            if (CSharpPropertyType == typeof(object))
                xsAttributeFactory.CreateAnyAttribute(
                    namespaceType: XsNamespaceType.Any
                );

            // <xs:attribute />
            return xsAttributeFactory.CreateAttribute(
                name: XName.LocalName,
                xsType: XsPropertyType,
                @default: m_defaultValue,
                use: m_isOptional ? XsAttributeUse.Optional : XsAttributeUse.Required
            );
        }
        internal XsComplexContent CreateXsElement(XsComplexContentFactory xsMemberFactory) {
            ASSERT.Condition(!m_isAttribute && !m_isContent);

            // <xs:element />
            return xsMemberFactory.CreateElement(
                name: XName.LocalName,
                type: XsPropertyType,
                @default: m_defaultValue,
                minOccurs: m_isOptional ? 0 : 1,
                maxOccurs: 1
            );
        }
        internal XsCSharpElement CreateRootXsElement(XsCSharpRootElementFactory xsRootElementFactory) {
            return xsRootElementFactory.CreateRootElement(XsCSharpPropertyType);
        }
        internal RestXmlProperty CreateRestXmlProperty(RestXmlPropertyFactory factory) {

            // teach simpleContent type creation and getValue and setValue
            return factory.CreateRestXmlProperty(
                simpleContent: o => {
                    return o.CreateSimpleType(
                        x => XsCSharpPropertyType.Deserialize(x),
                        x => XsCSharpPropertyType.Serialize(x)
                    );
                },

                getValue: o => m_csharpMember.Load().GetValue(o),
                setValue: (o, v) => m_csharpMember.Load().SetValue(o, v)
            );
        }
        
        public RestXmlCSharpType DeclaringType {
            get { return m_declaringType; }
        }
        public RestXmlCSharpProperty BaseProperty {
            get { return null; }
        }

        public CSharpMember CSharpMember {
            get { return m_csharpMember; }
        }
        public XsCSharpElement XsCSharpElement {
            get { return null; }
        }
        public XsCSharpAttribute XsCSharpAttribute {
            get { return null; }
        }
        public XsElement XsElement {
            get { return null; }
        }
        public XsAttribute XsAttribute {
            get { return null; }
        }
        public RestXmlProperty RestXmlProperty {
            get { return m_restXmlProperty.Value; }
        }

        public string Name {
            get { return m_name; }
        }
        public XName XName {
            get { return m_xName; }
        }

        public RestXmlCSharpType PropertyType {
            get { return m_propertyType.Value; }
        }
        public XsCSharpType XsCSharpPropertyType {
            get { return PropertyType.XsCSharpType; }
        }
        public CSharpType CSharpPropertyType {
            get { return XsCSharpPropertyType.CSharpType; }
        }
        public XsType XsPropertyType {
            get { return XsCSharpPropertyType.XsType; }
        }

        public int? Order {
            get { return RestXmlProperty.Order; }
        }
        public object DefaultValue {
            get { return m_defaultValue; }
        }
        public bool IsAttribute {
            get { return m_isAttribute; }
        }
        public bool IsContent {
            get { return m_isContent; }
        }
        public bool IsStreamable {
            get { return m_isStreamable; }
        }
        public bool IsOptional {
            get { return m_isOptional; }
        }

        public object GetValue(object value) {
            var method = m_csharpMember as CSharpMethod;
            if (method != null)
                return method.Load().Invoke(value, null);

            var property = (CSharpDataMember)m_csharpMember;
            return property.Load().GetValue(value);
        }

        public override string ToString() {
            return Name;
        }
    }

    public abstract class RestXmlCSharpLoaderMember {

        private readonly RestXmlCSharpLoader m_loader;
        private readonly Lazy<XsCSharpAssembly> m_xsCSharpAssembly;

        internal RestXmlCSharpLoaderMember(
            RestXmlCSharpLoader loader,
            Lazy<XsCSharpAssembly> xsCSharpAssembly) { 

            m_loader = loader;
            m_xsCSharpAssembly = xsCSharpAssembly;
        }

        public RestXmlCSharpLoader Loader {
            get { return m_loader; }
        }
        public XsCSharpAssembly XsCSharpAssembly {
            get { return m_xsCSharpAssembly.Value; }
        }

        public CSharpLoader CSharpLoader {
            get { return Loader.CSharpLoader; }
        }
        public XsLoader XsLoader {
            get { return XsCSharpLoader.XsLoader; }
        }
        public XsCSharpLoader XsCSharpLoader {
            get { return Loader.XsCSharpLoader; }
        }
        public RestXmlLoader RestXmlLoader {
            get { return Loader.RestXmlLoader; }
        }

        public CSharpAssembly CSharpAssembly {
            get { return XsCSharpAssembly.CSharpAssembly; }
        }
    }
    public abstract class RestXmlCSharpAssemblyMember : RestXmlCSharpLoaderMember {
        private readonly Lazy<XsCSharpSchema> m_xsCSharpSchema;

        internal RestXmlCSharpAssemblyMember(
            RestXmlCSharpLoader loader,
            Lazy<XsCSharpAssembly> xsCSharpAssembly,
            Func<RestXmlCSharpAssemblyMember, XsCSharpSchema> xsCSharpSchema)
            : base(loader, xsCSharpAssembly) {

            m_xsCSharpSchema = new Lazy<XsCSharpSchema>(() => {
                var result = xsCSharpSchema(this);
                ASSERT.Condition(result != null);
                return result;
            });
        }

        public XsCSharpSchema XsCSharpSchema {
            get { return m_xsCSharpSchema.Value; }
        }
        public RestXmlCSharpAssembly Assembly {
            get { return Loader.GetAssembly(XsCSharpAssembly); }
        }
        public XsSchema XsSchema {
            get { return XsCSharpSchema.XsSchema; }
        }
        public RestXmlSchema RestXmlSchema {
            get { return RestXmlLoader.GetSchema(XNamespace); }
        }

        public CSharpNamespace CSharpNamespace {
            get { return XsCSharpSchema.CSharpType.Namespace; }
        }
        public virtual XNamespace XNamespace {
            get { return XsCSharpSchema.XNamespace; }
        }
    }
    public abstract class RestXmlCSharpSchemaMember : RestXmlCSharpAssemblyMember {
        private readonly RestXmlCSharpSchema m_schema;

        internal RestXmlCSharpSchemaMember(
            RestXmlCSharpSchema schema
        ) : base(
            schema.Loader, 
            null,
            _ => schema.XsCSharpSchema
        ) { 

            m_schema = schema;
        }

        public RestXmlCSharpSchema Schema {
            get { return m_schema; } 
        }
    }
}
namespace King.Rest.Xml.CSharp.Reflection.Emit {

    public static class RestXmlCSharpResolver {
    
        public static Dictionary<string, string> GetPropertyNames(
            CSharpType schemaType,
            Type attributeType) {

            var properties = (
                from property in schemaType.Members(declaredOnly: true, isStatic: false)
                let attribute = property.GetAttribute(attributeType)
                where attribute != null
                let alias = (string)attribute.Name ?? property.Name
                select new { Key = alias, Value = property.Name }
            ).ToList();
            
            var propertyNames = properties.ToDictionary(o => o.Key, o => o.Value);

            return propertyNames;
        }

        public static CSharpMember ResolveProperty(
            object key,
            CSharpType schemaType,
            Dictionary<string, string> propertyNames,
            XNamespace xNamespace,
            Func<string, string> toCSharpName,
            XsForm xsForm,
            IEnumerable<Type> typesWithXNameProperty) {

            // CSharpProperty
            var member = key as CSharpMember;
            if (member == null) {

                // resolve name
                var name = XsCSharpResolver.ResolveName(
                    key: key,
                    xNamespace: xNamespace,
                    toCSharpName: toCSharpName,
                    xsForm: xsForm,
                    resolveXName: _ => {

                        // element, attribute
                        if (!typesWithXNameProperty.Contains(key.GetType()))
                            return null;

                        return (XName)((dynamic)key).XName;
                    }
                );

                // resolve alias
                name = propertyNames.GetValueOrDefault(name);
                if (name == null)
                    return null;

                // resolve property
                member = schemaType.GetMember(name, declaredOnly: true);
                if (member == null)
                    return null;
            }

            if (member.DeclaringType != schemaType)
                return null;

            return member;
        }
    }
}
